package xginplusws

import (
	"context"
	"net/http"
	"time"

	"gitee.com/xiaoyutab/xgotool/individual/xginplus"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

// 将 WS 长连接转化为gin可以直接插入的路由组
//
//	ws	处理 WS 方法的结构体列表
//	fc	针对此分组下的追加配置项
func Convert(ws Websocket, fc ...xginplus.ContextOptionFunc) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// 将 gin.context 对象转换为待使用的 xginplus.context 对象
		c := xginplus.To(ctx)
		for i := 0; i < len(fc); i++ {
			fc[i](c)
		}
		if ws == nil {
			c.JSON(c.Errorf("WebSocket处理结构体不能为空").GetCodeData())
			return
		}
		// 定义升级方式
		var upgrader = websocket.Upgrader{
			CheckOrigin: func(r *http.Request) bool {
				return true
			},
		}
		// 升级HTTP连接为WebSocket连接
		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			c.JSON(c.Errorf("Failed to upgrade WebSocket connection: %s", err.Error()).GetCodeData())
			return
		}
		defer conn.Close() // WS关闭
		defer ws.Close(c)  // WS关闭通知
		// 通知ws的打开事件钩子
		err = ws.Open(c)
		if err != nil {
			c.JSON(c.Errorf("Failed to WebSocket connection: %s", err.Error()).GetCodeData())
			return
		}
		// 创建控制信号【使用WithCancel创建带关闭功能的控制信号】
		ctx_bak, cancel := context.WithCancel(context.Background())
		defer cancel()
		// 消息发送监听
		go wsSendMessage(ws, c, conn, ctx_bak)
		// 消息监听
		for {
			// 读取客户端发送的消息
			_, msg, err := conn.ReadMessage()
			if err != nil {
				// 连接手动断开/意外断开
				break
			}
			// 没有获取到客户端给服务端发过来的消息
			if len(msg) <= 0 {
				continue
			}
			// 读取到了客户端发过来的消息，将之通知给ws客户端
			ws.Pull(c, msg)
		}
	}
}

// WS消息发送函数调用【因监听客户端消息时会阻塞，所以此处我们采用go携程的方式进行发送消息，以便客户端主动进行消息推送】
//
//	ws	ws消息获取结构体
//	c	gin请求结构体
//	con	WS链接，用来发送消息
//	ct	控制开启/关闭的控制信号
func wsSendMessage(ws Websocket, c *xginplus.Context, con *websocket.Conn, ct context.Context) {
	for {
		// 休眠20毫秒
		time.Sleep(time.Millisecond * 20)
		select {
		case <-ct.Done():
			return
		default:
			// 获取服务端发送给客户端的消息
			if byt, ok := ws.Push(c); ok {
				con.WriteJSON(byt)
			}
		}
	}
}
